package ar.edu.itba.pod.legajo45129;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.Logger;

import ar.edu.itba.pod.legajo45129.simul.communication.ClusterAdministrationImpl;
import ar.edu.itba.pod.legajo45129.simul.communication.ConnectionManagerImpl;
import ar.edu.itba.pod.legajo45129.simul.communication.MessageListenerImpl;
import ar.edu.itba.pod.legajo45129.simul.communication.SimulationCommunicationImpl;
import ar.edu.itba.pod.legajo45129.simul.communication.factory.AgentFactory;
import ar.edu.itba.pod.simul.communication.ConnectionManager;
import ar.edu.itba.pod.simul.communication.Message;
import ar.edu.itba.pod.simul.communication.MessageType;
import ar.edu.itba.pod.simul.communication.NodeAgentLoad;
import ar.edu.itba.pod.simul.communication.payload.Payload;
import ar.edu.itba.pod.simul.market.Resource;
import ar.edu.itba.pod.simul.time.TimeMappers;

public class Node implements Runnable, Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = 2265797460252352499L;
	
	private String nodeId;
	private ConnectionManager connectionManager;
	
	private static Logger logger = Logger.getLogger(Node.class);
	private static Options options = new Options();
	private static CommandLineParser parser = new GnuParser();
	private static Option send;
	private static Option broadcast;
	private static Option connectedNodes;
	private static Option inbox;
	private static Option broadcastInbox;
	private static Option addAgent;
	private static Option agents;
	private static Option minAgentLoad;
	private static Option connect;
	private static Option createGroup;
	private static Option disconnect;
	private static Option help;
	private static HelpFormatter formatter = new HelpFormatter();
	
	
	public Node(String nodeId) throws RemoteException, AlreadyBoundException {
		this.nodeId = nodeId;
		this.connectionManager = new ConnectionManagerImpl(nodeId, TimeMappers.oneSecondEach(6, TimeUnit.HOURS));
		
		Thread thread = new Thread(MessageProcessor.getInstance(connectionManager));
		thread.start();
			
	}

	public String getNodeId() {
		return nodeId;
	}

	public void setNodeId(String nodeId) {
		this.nodeId = nodeId;
	}

	public ConnectionManager getConnectionManager() {
		return connectionManager;
	}

	public void setConnectionManager(ConnectionManager connectionManager) {
		this.connectionManager = connectionManager;
	}
	
	private static void initOptions() {
		send = new Option("send", "send message");
		send.setArgs(1);
		options.addOption(send);
		
		broadcast = new Option("broadcast", "braodcast a message to entire cluster");
		broadcast.setArgs(1);
		options.addOption(broadcast);
		
		connectedNodes = new Option("connectedNodes", "show my connected nodes");
		options.addOption(connectedNodes);
		
		inbox = new Option("inbox", "check message inbox");
		options.addOption(inbox);

		broadcastInbox = new Option("broadcastInbox", "check broadcast inbox");
		options.addOption(broadcastInbox);
		
		addAgent = new Option("addAgent","add a agent");
		options.addOption(addAgent);
		
		agents = new Option("agents", "get agent count");
		options.addOption(agents);
		
		minAgentLoad = new Option("minAgentLoad", "get min count of agents of known nodes");
		options.addOption(minAgentLoad);
		
		// option group for connect and create cuz they r mutually exclusive options.
		connect = new Option("connect", "connect to group with given node id (URI) as entry point");
		connect.setArgs(1);
		options.addOption(connect);
		
		createGroup = new Option("createGroup", "create a group for this node");
		options.addOption(createGroup);
		
		disconnect = new Option("disconnect", "disconnect from the group");
		disconnect.setArgs(1);
		options.addOption(disconnect);
		
		help = new Option("help", "print options");
		options.addOption(help);
		
		
		
	}

	@Override
	public void run() {
		logger.info("node " + nodeId + " is up");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		CommandLine cmd;
		
		Message mockMessage = null;
		initOptions();
		
		
		while (true) {
			try {
				String line = br.readLine();
				String[] args = line.split(" ");
				
				cmd = parser.parse(options, args);
				
				if (cmd.hasOption(connect.getOpt())) {
					connectionManager.getClusterAdmimnistration().connectToGroup(cmd.getOptionValue(connect.getOpt()));
				}
				
				if (cmd.hasOption(createGroup.getOpt())) {
					connectionManager.getClusterAdmimnistration().createGroup();
				}

				if (cmd.hasOption(send.getOpt())) {
					String destNodeId = cmd.getOptionValue(send.getOpt());
					logger.info("sending mock msg to: " + destNodeId);
					mockMessage = new Message(this.nodeId, System.currentTimeMillis(), 
							MessageType.RESOURCE_REQUEST, new MockPayload());
					
					connectionManager.getGroupCommunication().send(mockMessage, destNodeId);
				} 
				
				if (cmd.hasOption(broadcast.getOpt())) {
					logger.info("sending broadcast mock msg");
					mockMessage = new Message(this.nodeId, System.currentTimeMillis(), 
							MessageType.RESOURCE_REQUEST, new MockPayload());
					connectionManager.getGroupCommunication().broadcast(mockMessage);
				}
				
				if (cmd.hasOption(connectedNodes.getOpt())) {
					logger.info("my connected nodes:");
					ClusterAdministrationImpl ca = (ClusterAdministrationImpl) connectionManager.getClusterAdmimnistration();
					Iterator<String> connectedNodes = ca.getConnectedNodes();
					while (connectedNodes.hasNext()) {
						logger.info(connectedNodes.next());
					}
				}
				
				if (cmd.hasOption(inbox.getOpt())) {
					logger.info("message inbox:");
					MessageListenerImpl ml = (MessageListenerImpl) this.connectionManager.getGroupCommunication().getListener();
					Iterator<Message> it = ml.getInbox().iterator();
					while (it.hasNext()) {
						logger.info(it.next());
					}
				}
				
				if (cmd.hasOption(addAgent.getOpt())) {
					
					// TODO dps hacer que aniade cada tipo de agentes (Factory, Consumer, Producer)
					// porahora mockeado
					
					this.connectionManager.getSimulationCommunication().startAgent(AgentFactory.SimpleConsumerAgent("pepe", new Resource("categoryResoruce", "nameResource"), 10, 1, TimeUnit.DAYS));
					logger.info("agent added");
				}
				
				
				if (cmd.hasOption(agents.getOpt())) {
					SimulationCommunicationImpl sci = (SimulationCommunicationImpl) this.connectionManager.getSimulationCommunication();
					logger.info("agents in this node: " + sci.getAgentsCount());
				}
				
				if (cmd.hasOption(minAgentLoad.getOpt())) {
					NodeAgentLoad nodeAgentLoad = this.connectionManager.getSimulationCommunication().getMinimumNodeKnownLoad();
					logger.info("node " + nodeAgentLoad.getNodeId() + " has the minimun agents count " + nodeAgentLoad.getNumberOfAgents());
					
				}
				
				if (cmd.hasOption(disconnect.getOpt())) {
					connectionManager.getClusterAdmimnistration().disconnectFromGroup(cmd.getOptionValue(disconnect.getOpt()));
					logger.info("disconnected from the group");
				}
				
				if (cmd.hasOption(help.getOpt())) {
					formatter.printHelp("simul.jar", options);
				}
				
				
				
					
				
			} catch (ParseException e) {
				logger.error("parse commmand error: " + e.getMessage());
//				System.exit(1);
			} catch (RemoteException e) {
				logger.error("error sending message: " + e);
//				System.exit(1);
			} catch (IOException e) {
				logger.error(e.getMessage());
				System.exit(1);
			}
		}
	}	
	
	@SuppressWarnings("serial")
	private class MockPayload implements Payload {
		
	}
}
